/*global env: true */
"use strict";

// 引入 Node.js 和 JSDoc 相关模块
const fs = require("jsdoc/fs");
const helper = require("jsdoc/util/templateHelper");
const path = require("jsdoc/path");
const taffy = require("taffydb").taffy;
const template = require("jsdoc/template");

// 从 helper 模块中提取一些常用的函数
const { 
  htmlsafe, 
  linkto, 
  resolveAuthorLinks, 
  find: helperFind,
  getSignatureParams,
  getSignatureReturns,
  getSignatureTypes,
  getAttribs,
  getAncestorLinks: helperGetAncestorLinks,
  createLink,
  toTutorial,
  resolveLinks: helperResolveLinks,
  getUniqueFilename,
  registerLink,
  setTutorials,
  prune,
  addEventListeners,
  getMembers,
  longnameToUrl
} = helper;

const hasOwnProp = Object.prototype.hasOwnProperty;

// 全局变量 data 和 view
let data;
let view;

// 输出目录
const outdir = env.opts.destination;

/**
 * 根据条件查找文档项
 * @param {Object} spec - 查找条件
 * @returns {Array} 匹配的文档项数组
 */
function find(spec) {
  return helperFind(data, spec);
}

/**
 * 创建教程链接
 * @param {string} tutorial - 教程名称
 * @returns {string} 教程链接HTML
 */
function tutoriallink(tutorial) {
  return toTutorial(tutorial, null, {
    tag: "em",
    classname: "disabled",
    prefix: "Tutorial: ",
  });
}

/**
 * 获取祖先链接（用于类、命名空间等）
 * @param {Object} doclet - 文档项
 * @returns {Array} 祖先链接数组
 */
function getAncestorLinks(doclet) {
  return helperGetAncestorLinks(data, doclet);
}

/**
 * 将哈希值转换为链接
 * @param {Object} doclet - 文档项
 * @param {string} hash - 哈希值
 * @returns {string} 链接HTML或原始哈希值
 */
function hashToLink(doclet, hash) {
  if (!/^(#.+)/.test(hash)) {
    return hash;
  }

  let url = createLink(doclet);
  url = url.replace(/(#.+|$)/, hash);
  return `<a href="${url}">${hash}</a>`;
}

/**
 * 判断是否需要签名（函数、类、模块等）
 * @param {Object} doclet - 文档项
 * @returns {boolean} 是否需要签名
 */
function needsSignature(doclet) {
  // function and class definitions always get a signature
  if (["function", "class", "module"].includes(doclet.kind)) {
    return true;
  }
  
  // typedefs that contain functions get a signature, too
  if (doclet.kind === "typedef" && 
      doclet.type && 
      doclet.type.names && 
      doclet.type.names.some(name => name.toLowerCase() === "function")) {
    return true;
  }

  return false;
}

/**
 * 添加函数参数到签名
 * @param {Object} f - 函数文档项
 */
function addSignatureParams(f) {
  const params = getSignatureParams(f, "optional");
  f.signature = (f.signature || "") + "(" + params.join(", ") + ")";
}

/**
 * 添加返回类型到签名
 * @param {Object} f - 函数文档项
 */
function addSignatureReturns(f) {
  const returnTypes = getSignatureReturns(f);
  
  f.signature = `<span class="signature">${f.signature || ""}</span>`;
  
  if (returnTypes.length) {
    f.signature += ` &rarr; <span class="type-signature returnType">${
      returnTypes.join("|")
    }</span>`;
  }
}

/**
 * 添加类型信息到签名
 * @param {Object} f - 函数文档项
 */
function addSignatureTypes(f) {
  const types = getSignatureTypes(f);
  
  f.signature = (f.signature || "") +
    `<span class="type-signature">${types.length ? " : " + types.join("|") : ""}</span>`;
}

/**
 * 添加属性（如 deprecated 等）
 * @param {Object} f - 文档项
 */
function addAttribs(f) {
  const attribs = [...getAttribs(f)];
  
  if (f.deprecated) {
    attribs.push("deprecated");
  }

  if (attribs.length) {
    f.attribs = attribs
      .map(attrib => 
        `<span class="type-signature attribute-${attrib}">${htmlsafe(attrib)}</span>`
      )
      .join(" ");
  }
}

/**
 * 缩短文件路径
 * @param {Object} files - 文件对象
 * @param {string} commonPrefix - 公共前缀
 * @returns {Object} 处理后的文件对象
 */
function shortenPaths(files, commonPrefix) {
  Object.keys(files).forEach(file => {
    files[file].shortened = files[file].resolved
      .replace(commonPrefix, "")
      // always use forward slashes
      .replace(/\\/g, "/");
  });

  return files;
}

/**
 * 从 doclet 获取文件路径
 * @param {Object} doclet - 文档项
 * @returns {string|null} 文件路径或null
 */
function getPathFromDoclet(doclet) {
  if (!doclet.meta) {
    return null;
  }

  return doclet.meta.path && doclet.meta.path !== "null"
    ? path.join(doclet.meta.path, doclet.meta.filename)
    : doclet.meta.filename;
}

/**
 * 生成 HTML 页面
 * @param {string} title - 页面标题
 * @param {Array} docs - 文档项数组
 * @param {string} filename - 文件名
 * @param {boolean} resolveLinks - 是否解析链接
 */
function generate(title, docs, filename, resolveLinks = true) {
  const docData = {
    filename: filename,
    title: title,
    docs: docs,
  };

  const outpath = path.join(outdir, filename);
  let html = view.render("container.tmpl", docData);

  if (resolveLinks) {
    html = helperResolveLinks(html); // turn {@link foo} into <a href="foodoc.html">foo</a>
  }

  fs.writeFileSync(outpath, html, "utf8");
}

/**
 * 查找与模块同名的类或函数，并将其附加到模块 doclet 上
 * @private
 * @param {Array.<module:jsdoc/doclet.Doclet>} doclets - 类和函数数组
 * @param {Array.<module:jsdoc/doclet.Doclet>} modules - 模块数组
 */
function attachModuleSymbols(doclets, modules) {
  const symbols = {};

  // build a lookup table
  doclets.forEach(symbol => {
    symbols[symbol.longname] = symbol;
  });

  modules.forEach(module => {
    if (symbols[module.longname]) {
      module.module = symbols[module.longname];
      module.module.name = 
        module.module.name.replace("module:", 'require("') + '")';
    }
  });
}

/**
 * 创建导航侧边栏
 * @param {object} members - 成员对象
 * @return {string} 导航栏 HTML
 */
function buildNav(members) {
  let nav = '<div id="ClassList">';
  const seen = {};
  
  const items = [...members.modules, ...members.classes, ...members.globals, ...members.namespaces]
    .sort((a, b) => 
      a.longname.toLowerCase().localeCompare(b.longname.toLowerCase())
    );

  const addItems = (itemsToAdd) => {
    itemsToAdd.forEach(m => {
      if (!hasOwnProp.call(seen, m.longname)) {
        nav += `<li data-name="${m.name}">${linkto(m.longname, m.name)}</li>`;
      }
      seen[m.longname] = true;
    });
  };

  if (process.env.PACKAGES) {
    process.env.PACKAGES.split(",").forEach(packageName => {
      nav += `<h5>${packageName}</h5><ul>`;
      addItems(items.filter(item => item.meta?.package === packageName));
      nav += "</ul>";
    });
  } else {
    nav += "<ul>";
    addItems(items);
    nav += "</ul>";
  }

  nav += "</div>";
  return nav;
}
/**
 * 复制源文件并生成 HTML 页面
 * @param {string} sourcePath - 源文件路径
 * @param {string} shortenedPath - 缩短后的路径
 * @param {string} sourceOutDir - 源文件输出目录
 */
function copySourceFile(sourcePath, shortenedPath, sourceOutDir) {
  const destPath = path.join(sourceOutDir, shortenedPath);
  fs.mkPath(path.dirname(destPath));
  
  // 读取源文件内容并创建带语法高亮的 HTML 版本
  try {
    const sourceCode = fs.readFileSync(sourcePath, 'utf8');
    // 转义HTML特殊字符以安全显示
    const escapedCode = sourceCode
      .replace(/&/g, '&amp;')
      .replace(/</g, '&lt;')
      .replace(/>/g, '&gt;');
      
    const htmlContent = `<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <title>Source: ${shortenedPath}</title>
  <link rel="stylesheet" href="../styles/jsdoc-default.css">
  <link rel="stylesheet" href="../styles/prism.css">
</head>
<body class="source-page">
  <h1 class="source-page-title">Source: ${shortenedPath}</h1>
  <pre class="line-numbers language-javascript"><code class="language-javascript">${escapedCode}</code></pre>
  <script src="../javascript/prism.js"></script>
</body>
</html>`;
    
    fs.writeFileSync(destPath + '.html', htmlContent, 'utf8');
  } catch (e) {
    console.error('Error copying source file: ' + sourcePath, e);
  }
}

/**
 * 主要发布函数
 * @param {TAFFY} taffyData - 数据库
 * @param {object} opts - 配置选项
 * @param {Tutorial} tutorials - 教程数据
 */
exports.publish = function (taffyData, opts, tutorials) {
  data = taffyData;

  const conf = env.conf.templates || {};
  conf.default = conf.default || {};

  const templatePath = opts.template;
  view = new template.Template(templatePath + "/tmpl");

  // 获取自定义logo配置
  const templateConf = env.conf.templates || {};
  view.logo = templateConf.logo || "icons/logo.png";
  view.logoAlt = templateConf.logoAlt || "Logo";
  
  // 获取备案信息配置，设置默认值
  view.beian = {
    icp: templateConf.beian?.icp || null,  // 默认为 null 而不是具体值
    police: templateConf.beian?.police || null,
    redirect: templateConf.beian?.redirect || null
  };

  // claim some special filenames in advance, so the All-Powerful Overseer of Filename Uniqueness
  // doesn't try to hand them out later
  const indexUrl = getUniqueFilename("index");
  // don't call registerLink() on this one! 'index' is also a valid longname

  const globalUrl = getUniqueFilename("global");
  registerLink("global", globalUrl);

  // set up templating
  view.layout = "layout.tmpl";

  // set up tutorials for helper
  setTutorials(tutorials);

  data = prune(data);
  data.sort("longname, version, since");
  addEventListeners(data);

  const sourceFiles = {};
  const sourceFilePaths = [];
  
  data().each(doclet => {
    doclet.attribs = "";

    doclet.longname = doclet.longname.replace(/^module:/, "");
    if (doclet.memberof) {
      doclet.memberof = doclet.memberof.replace(/^module:/, "");
    }

    // build a list of source files
    const sourcePath = doclet.meta ? getPathFromDoclet(doclet) : null;
    if (sourcePath) {
      sourceFiles[sourcePath] = {
        resolved: sourcePath,
        shortened: null,
      };
      
      if (!sourceFilePaths.includes(sourcePath)) {
        sourceFilePaths.push(sourcePath);
      }
    }
    
    // 处理示例代码 - 确保默认生成代码示例
    if (doclet.examples) {
      doclet.examples = doclet.examples.map(example => {
        let caption = "";
        let code = example;

        const match = example.match(
          /^\s*<caption>([\s\S]+?)<\/caption>(\s*[\n\r])([\s\S]+)$/i
        );
        
        if (match) {
          caption = match[1];
          code = match[3];
        }

        return {
          caption: caption,
          code: code,
        };
      });
    }
    
    // 处理 see 链接
    if (doclet.see) {
      doclet.see.forEach((seeItem, i) => {
        doclet.see[i] = hashToLink(doclet, seeItem);
      });
    }
  });

  // update outdir if necessary, then create outdir
  const packageInfo = (find({ kind: "package" }) || [])[0];
  let finalOutdir = outdir;
  if (packageInfo && packageInfo.name) {
    finalOutdir = path.join(outdir, packageInfo.name, packageInfo.version);
  }
  fs.mkPath(finalOutdir);

  // copy the template's static files to outdir
  const fromDir = path.join(templatePath, "static");
  const staticFiles = fs.ls(fromDir, 3);

  staticFiles.forEach(fileName => {
    const toDir = fs.toDir(fileName.replace(fromDir, finalOutdir));
    fs.mkPath(toDir);
    fs.copyFileSync(fileName, toDir);
  });

  // shorten paths if we have source files
  if (sourceFilePaths.length) {
    const commonPrefix = path.commonPrefix(sourceFilePaths);
    shortenPaths(sourceFiles, commonPrefix);
  }
  
  // 创建源文件目录
  const sourceOutDir = path.join(finalOutdir, 'source');
  fs.mkPath(sourceOutDir);
  
  data().each(doclet => {
    const url = createLink(doclet);
    registerLink(doclet.longname, url);

    // replace the filename with a shortened version of the full path
    if (doclet.meta) {
      const docletPath = getPathFromDoclet(doclet);
      const shortenedPath = sourceFiles[docletPath]?.shortened;
      
      if (shortenedPath) {
        doclet.meta.filename = shortenedPath;
        
        // 生成源文件链接
        const sourceUrl = path.join('source', shortenedPath);
        doclet.meta.sourceUrl = sourceUrl;
          
        if (process.env.PACKAGES) {
          doclet.meta.package = process.env.PACKAGES.split(",").find(
            package => doclet.meta.sourceUrl?.indexOf(package) > -1
          );
        }
      }
    }
  });
  
  // 复制源文件到输出目录
  Object.keys(sourceFiles).forEach(filePath => {
    const shortenedPath = sourceFiles[filePath].shortened;
    if (shortenedPath) {
      copySourceFile(filePath, shortenedPath, sourceOutDir);
    }
  });

  // do this after the urls have all been generated
  data().each(doclet => {
    const url = longnameToUrl[doclet.longname];

    if (url?.indexOf("#") > -1) {
      doclet.id = longnameToUrl[doclet.longname].split(/#/).pop();
    } else {
      doclet.id = doclet.name;
    }

    if (needsSignature(doclet)) {
      addSignatureParams(doclet);
      addSignatureReturns(doclet);
      addAttribs(doclet);
    }
  });

  data().each(doclet => {
    doclet.ancestors = getAncestorLinks(doclet);

    if (doclet.kind === "member") {
      addSignatureTypes(doclet);
      addAttribs(doclet);
    }

    if (doclet.kind === "constant") {
      addSignatureTypes(doclet);
      addAttribs(doclet);
      doclet.kind = "member";
    }
  });

  const members = getMembers(data);

  // add template helpers
  view.find = find;
  view.linkto = linkto;
  view.resolveAuthorLinks = resolveAuthorLinks;
  view.tutoriallink = tutoriallink;
  view.htmlsafe = htmlsafe;

  // once for all
  view.nav = buildNav(members);
  attachModuleSymbols(
    find({ kind: ["class", "function"], longname: { left: "module:" } }),
    members.modules,
  );

  if (members.globals.length) {
    generate("Global", [{ kind: "globalobj" }], globalUrl);
  }

  // index page displays information from package.json and lists files
  const files = find({ kind: "file" });
  const packages = find({ kind: "package" });

  const origLayout = view.layout;
  view.layout = "indexLayout.tmpl";
  generate(
    "Index",
    [...packages, {
      kind: "mainpage",
      readme: opts.readme,
      longname: opts.mainpagetitle ? opts.mainpagetitle : "Main Page",
    }, ...files],
    indexUrl,
  );
  view.layout = origLayout;

  // set up the lists that we'll use to generate pages
  const classes = taffy(members.classes);
  const modules = taffy(members.modules);
  const namespaces = taffy(members.namespaces);
  const globals = taffy(members.globals);

  const typesJson = {};

  Object.keys(longnameToUrl).forEach(longname => {
    let items = helperFind(classes, { longname: longname });

    if (!items.length) {
      items = helperFind(modules, { longname: longname });
    }

    if (!items.length) {
      items = helperFind(namespaces, { longname: longname });
    }

    if (!items.length) {
      items = helperFind(globals, { longname: longname });
    }

    if (items.length) {
      const title = items[0].name;
      const filename = longnameToUrl[longname];
      generate(title, items, filename);

      const titleLower = title.toLowerCase();
      typesJson[titleLower] = typesJson[titleLower] || [];
      typesJson[titleLower].push(filename);

      const memberItems = find({ kind: ["function", "member"], memberof: longname });
      memberItems.forEach(member => {
        const memberId = member.id;
        let memberLower = memberId.toLowerCase();
        const firstChar = memberLower.charAt(0);
        
        if (firstChar === "." || firstChar === "~") {
          memberLower = memberLower.substring(1);
        }

        typesJson[memberLower] = typesJson[memberLower] || [];
        typesJson[memberLower].push(`${filename}#${memberId}`);
      });
    }
  });

  fs.writeFileSync(path.join(finalOutdir, "types.txt"), JSON.stringify(typesJson), "utf8");
};